package com.cw.lang.common.utils;


import java.net.InetAddress;
import java.util.Random;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.cw.lang.common.exception.BizException;

/**
 * snowflake(雪花)算法Id生成
 *
 * @author chenw
 * @version 1.0.0
 * @date 2019-09-07 16:48
 */
public class IdGenerator {
    public static void main(String[] args) {
        System.out.println(IdGenerator.nextId());
    }
    private static final Logger logger = LoggerFactory.getLogger(IdGenerator.class);
    /** 开始时间截 (2019-01-01) */
    private static final long epoch = 1546272000000L;

    /** 机器id所占的位数 10位的长度最多支持部署1024个节点*/
    private static final long workerIdBits = 8L;

    /** 应用实例id所占的位数 */
    private static final long applicationIdBits = 4L;

    /** 序列所占位数 */
    private static final long sequenceBits = 10L;

    /** 支持的最大机器id  1023L*/
    private static final long maxWorkerId = ~ (-1L << workerIdBits);

    /** 支持的实例标识id  31L*/
    private static final long maxApplicationId = ~ (-1L << applicationIdBits);

    /** 序列最大位数     1023L*/
    private static final long sequenceMask = ~ (-1L << sequenceBits);


    /** 毫秒内序列(0~4095) */
    private static long sequence = 0L;

    /** 机器ID向左移10位 10L*/
    private static final long workerIdShift = sequenceBits;

    /** 时间截向左移25位(10+10+5) 25*/
    private static final long timestampShift = sequenceBits + workerIdBits + applicationIdBits;

    /** 数据标识id向左移20位*/
    private static final long applicationIdShift = sequenceBits + workerIdBits;

    /** 上次生成ID的时间截 */
    private static long lastTimestamp = -1L;
    private static String workerId = null;
    private static final Random random = new Random();
    public static Long nextId() {
       return nextId(0L,0L);
    }
    public static String nextStrId() {
        return nextId(0L,0L).toString();
    }
    /**
     * 获取id
     *
     * @param workerId  0-1023  workerId为空则使用部署该Id生产器的服务器IP计算出workId<BR>
     * @param applicationId 0-31   applicationId为空则使用当前时间计算出applicationId<BR>
     * @return
     */
    public static synchronized Long nextId(Long workerId, Long applicationId) {
        if(workerId == null){
            workerId = getWorkerId();
        }

        if (workerId > maxWorkerId && workerId < 0L) {
            throw new BizException("999999",String.format("worker Id can't be greater than %d or less than 0", 1023L));
        }

        if(applicationId == null || applicationId > maxApplicationId || applicationId < 0L){
            applicationId = getApplicationId(applicationId);
            logger.warn("instance Id can't be greater than 31 or less than 0 , use system math instance Id:{}" , applicationId);
        }
        long timestamp = System.currentTimeMillis();

        //如果是同一时间生成的，则进行毫秒内序列
        if (lastTimestamp == timestamp) {
            //相同毫秒内，序列号自增
            sequence = (sequence + 1L) & sequenceMask;

            //毫秒内序列溢出 ,同一毫秒的序列数已经达到最大
            if (sequence == 0L) {
                //阻塞到下一个毫秒,获得新的时间戳
                timestamp = nextMillis(lastTimestamp);
            }
        } else {
            //时间戳改变，毫秒内序列重置
            sequence = 0;
        }

        if (timestamp < lastTimestamp) {
            logger.error(String.format("clock moved backwards.Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
            throw new BizException("999999",String.format("clock moved backwards.Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
        } else {
            lastTimestamp = timestamp;
            return (timestamp - epoch << timestampShift) | (applicationId << applicationIdShift) | (workerId << workerIdShift) | sequence;
        }

    }

    /**
     * 阻塞到下一个毫秒，直到获得新的时间戳
     *
     * @param lastTimestamp 上次生成ID的时间截
     * @return 当前时间戳
     */
    private static long nextMillis(long lastTimestamp) {
        long timestamp = System.currentTimeMillis();
        while (timestamp <= lastTimestamp) {
            timestamp = System.currentTimeMillis();
        }
        return timestamp;
    }

    /**
     * 系统自动applicationId
     *
     * @param data
     * @return
     */
    public static Long getApplicationId(Object data){
        Long applicationId = null;
        if(data != null){
            int hashVal = data.hashCode();
            applicationId = Long.valueOf(hashVal % maxApplicationId);
        }else{
            applicationId = System.currentTimeMillis() % maxApplicationId;
        }
        return applicationId;
    }

    public static Long getWorkerId() {
        if (StringUtils.isNotEmpty(workerId)) {
            return Long.valueOf(workerId);
        } else {
            try {
                if (StringUtils.isEmpty(workerId)) {
                    InetAddress localHost = InetAddress.getLocalHost();
                    String hostAddress = localHost.getHostAddress();
                    int lastIndex = hostAddress.lastIndexOf(".") + 1;
                    String subHostAddress = hostAddress.substring(0, lastIndex - 1);
                    int lastISecondndex = subHostAddress.lastIndexOf(".") + 1;
                    workerId = String.valueOf(Integer.parseInt(subHostAddress.substring(lastISecondndex, subHostAddress.length())) + Integer.parseInt(hostAddress.substring(lastIndex, hostAddress.length())));
                }
            } catch (Exception e) {
                logger.error("error ip generate app.worker.id.", e);
            } finally {
                if (StringUtils.isEmpty(workerId)) {
                    logger.warn("use random to generate generate app.worker.id");
                    workerId = String.valueOf(random.nextInt(1023));
                }
            }

            return Long.valueOf(workerId);
        }
    }
}

